package com.whosly.mysql.protocol.packet.impl;

import com.google.common.base.Preconditions;
import com.whosly.mysql.protocol.Capability;
import com.whosly.mysql.protocol.MySQLAuthPluginData;
import com.whosly.mysql.protocol.MySQLStatusFlag;
import com.whosly.mysql.protocol.config.MySQLProtocolConfig;
import com.whosly.mysql.protocol.jdbc.MySQLMessage;
import com.whosly.mysql.protocol.packet.jdbc.MySQLPacket;
import com.whosly.mysql.protocol.packet.util.NettyBufferUtil;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import lombok.Setter;
import org.apache.commons.lang3.StringUtils;

import java.nio.charset.StandardCharsets;

/**
 *
 * @author 风杨
 * @see <a href="yueny09@163.com">mailTo: yueny09@163.com</a>
 */
public class HandshakePacket extends MySQLPacket
{
    public final byte protocolVersion = MySQLProtocolConfig.PROTOCOL_VERSION;

    public String serverVersion;
    /**
     * threadId, is also connectionId
     */
    public long threadId;

    // also is maxPacketSize
    public int capabilityFlagsLower;

    public byte serverCharsetIndex;

    // MySQLStatusFlag.SERVER_STATUS_AUTOCOMMIT;
    public MySQLStatusFlag statusFlag;

    /**
     * seed
     *
     * seed 8 + scramble_buff 12
     */
    @Setter
    private MySQLAuthPluginData authPluginData;

    @Setter
    private int capabilityFlagsUpper;

    @Setter
    private String authPluginName;

    @Override
    public void read(byte[] bodyBytes)
    {
        readBody(new MySQLMessage(bodyBytes));
    }

    private void readBody(MySQLMessage mm) {
        Preconditions.checkArgument(protocolVersion == mm.read(), "protocolVersion is error");
        serverVersion = mm.readStringWithNull();
        threadId = mm.readUB4();

        // is  seed
        final byte[] authPluginDataPart1 = mm.readBytesWithNull();

        capabilityFlagsLower = mm.readUB2();

        /* New protocol with 16 bytes to describe server characteristics */
        serverCharsetIndex = mm.read();
        statusFlag = MySQLStatusFlag.valueOf(mm.readUB2());

        capabilityFlagsUpper = mm.readUB2();
        // for extended sever capabilities and plug-in based authentication 2 bytes integer sever capabilities
        int capabilities = capabilityFlagsLower | (capabilityFlagsUpper << 16);

        mm.move(1);
        mm.move(10);

        if (isClientSecureConnection()) {
            authPluginData = new MySQLAuthPluginData(
                    authPluginDataPart1,
                    // is restOfScrambleBuff
                    readAuthPluginDataPart2(mm)
            );
        }

        if (isClientPluginAuth()) {
            // extended authentication plug-in name
            authPluginName = readAuthPluginName(mm);
        }
    }

    @Override
    public ByteBuf writeBuf()
    {
        ByteBuf buffer = Unpooled.buffer(calcPacketSize());

        return writeBuf(buffer);
    }

    @Override
    public ByteBuf writeBuf(ByteBuf buffer)
    {
        NettyBufferUtil.writeUB3(buffer, buffer.capacity());
        buffer.writeByte(getPackId());

        writeBody(buffer);

        return buffer;
    }

    public void write(final ChannelHandlerContext ctx) {
        int size = calcPacketSize();

        // default init 256,so it can avoid buff extract
        final ByteBuf buffer = ctx.alloc().buffer(size);
        NettyBufferUtil.writeUB3(buffer, size);
        buffer.writeByte(getPackId());

        writeBody(buffer);

        // just io , so we don't use thread pool
        ctx.writeAndFlush(buffer);
    }

    private void writeBody(ByteBuf buffer){
        buffer.writeByte(protocolVersion);
        NettyBufferUtil.writeWithNull(buffer, parseBytes(serverVersion));
        NettyBufferUtil.writeUB4(buffer, threadId);
        NettyBufferUtil.writeWithNull(buffer, authPluginData.getAuthPluginDataPart1());

        //服务器权能标志
        NettyBufferUtil.writeUB2(buffer, capabilityFlagsLower);
        buffer.writeByte(serverCharsetIndex);
        NettyBufferUtil.writeUB2(buffer, statusFlag.getValue());

        // 服务器权能标志(高16位).
        // 2 bytes integer sever capabilities extended server capacities, 写进去的其实是serverCapability的高16bit.
        // capabilityFlagsUpper = capabilities >>> 16
        NettyBufferUtil.writeUB2(buffer, capabilityFlagsUpper);

        // 挑战长度. 此处被使用, 作为认证插件
        int clientPluginAuthLength = isClientPluginAuth() ?
                authPluginData.getAuthenticationPluginData().length + 1 : 0;
        buffer.writeByte(clientPluginAuthLength);
//        // or
//        //buffer.writeInt(0b1000_0000_0000_1000);
//        writeByte((byte) (MYSQL_NATIVE_PASSWORD.length()));

        buffer.writeBytes(FILLER_10);

        //  12 + 1（0x00表示结束)	挑战随机数, rest of scramble_buff (4.1)（第二部分）	用于后续账户密码验证
        if (isClientSecureConnection()) {
            NettyBufferUtil.writeWithNull(buffer, authPluginData.getAuthPluginDataPart2());
        }

        if (isClientPluginAuth()) {
            NettyBufferUtil.writeWithNull(buffer, authPluginName, StandardCharsets.UTF_8);
//        // or
//            // extended authentication plug-in name
//            writeString(MYSQL_NATIVE_PASSWORD);
        }
    }

    private byte[] readAuthPluginDataPart2(final MySQLMessage payload) {
        return isClientSecureConnection() ? payload.readBytesWithNull() : new byte[0];
    }

    private String readAuthPluginName(final MySQLMessage payload) {
        return isClientPluginAuth() ? payload.readStringWithNull() : null;
    }

    private boolean isClientSecureConnection() {
        return Capability.isEnabled(capabilityFlagsLower, Capability.CanDo4_1Authentication);
    }

    private boolean isClientPluginAuth() {
        return 0 != (capabilityFlagsUpper & Capability.calculateHandshakeCapabilityFlagsUpper());
    }

    @Override
    public int calcPacketSize() {
        int size = 1;
        size += serverVersion.length() + 1;//  len = strlen(server_version) + 1
        size += 4; // 4
        size += authPluginData.getAuthPluginDataPart1().length + 1;// 8 + 1
        size += 18;// 2+1+2+2+1+10
        size += authPluginData.getAuthPluginDataPart2().length + 1;// 12 + 1
        size += StringUtils.isEmpty(authPluginName) ? 1 : authPluginName.length() + 1;

        return size;
    }


    @Override
    public String getPacketInfo() {
        return "MySQL Handshake Packet";
    }

}
